/*
Copyright (c) 2025 HeZongLun
NahidaProject is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan
PSL v2.
You may obtain a copy of Mulan PSL v2 at:
		 http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
*/

#include "ChooseDialog.h"

std::wstring NahidaProject::ChooseDialog::BasicMenu::TruncateString(const std::wstring& str, int maxSize)
{
	if (str.size() <= maxSize) {
		return str;
	}
	int maxSizeWithReplacement = maxSize - TRUNC_CHARS;
	std::wstring truncatedStr = str.substr(0, maxSizeWithReplacement);
	if (truncatedStr.length() < str.length()) {
		truncatedStr += L"~" + str.substr(str.length() - TRUNC_CHARS);
	}
	return truncatedStr;
}

void NahidaProject::ChooseDialog::BasicMenu::HideConsoleCursor() {
	HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursorInfo;
	GetConsoleCursorInfo(hConsole, &cursorInfo);
	cursorInfo.bVisible = false;
	SetConsoleCursorInfo(hConsole, &cursorInfo);
}

void NahidaProject::ChooseDialog::BasicMenu::ShowConsoleCursor() {
	HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursorInfo;
	GetConsoleCursorInfo(hConsole, &cursorInfo);
	cursorInfo.bVisible = true;
	SetConsoleCursorInfo(hConsole, &cursorInfo);
}

int NahidaProject::ChooseDialog::BasicMenu::GetConsoleLineSize() {
	HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) {
		return -1;
	}
	return csbi.dwSize.X;
}

// discard any unread input
void NahidaProject::ChooseDialog::BasicMenu::ClearInputBuffer() {
	HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
	FlushConsoleInputBuffer(hInput);
}

void NahidaProject::ChooseDialog::BasicMenu::ClearLine() {
	std::cout << "\033[2K"; // overwrite current line
	std::cout << "\033[0G"; // Move cursor to beginning of line
}

void NahidaProject::ChooseDialog::BasicMenu::ClearLeft(int chars) {
	std::cout << "\033[" << chars << "D"; // Move cursor n characters to the left
	for (int i = 0; i < chars; ++i) {
		std::cout << ' '; // overwrite with spaces
	}
	std::cout << "\033[" << chars << "D"; // reset console cursor to initial position
}

void NahidaProject::ChooseDialog::BasicMenu::ClearRight(int chars) {
	for (int i = 0; i < chars; ++i) {
		std::cout << ' '; // overwrite with spaces
	}
	std::cout << "\033[" << chars << "D"; // reset console cursor to initial position
}

void NahidaProject::ChooseDialog::BasicMenu::MoveConsoleCursorLeft(int chars) {
	if (chars) {
		std::cout << "\033[" << chars << "D";
	}
}

void NahidaProject::ChooseDialog::BasicMenu::MoveConsoleCursorRight(int chars) {
	if (chars) {
		std::cout << "\033[" << chars << "C";
	}
}

void NahidaProject::ChooseDialog::BasicMenu::MoveConsoleCursorUp(int lines) {
	if (lines) {
		std::cout << "\033[" << lines << "A";
	}
}

void NahidaProject::ChooseDialog::BasicMenu::MoveConsoleCursorDown(int lines) {
	if (lines) {
		std::cout << "\033[" << lines << "B";
	}
}

void NahidaProject::ChooseDialog::BasicMenu::ToggleOption(Option& option) {
	option._selected = !option._selected;
}

void NahidaProject::ChooseDialog::BasicMenu::SelectOption(Option& option) {
	option._selected = TRUE;
}

void NahidaProject::ChooseDialog::BasicMenu::UnselectOption(Option& option) {
	option._selected = FALSE;
}

void NahidaProject::ChooseDialog::VerticalMenu::RenderInstruction(){
	std::wcout << m_instruction;
}

void NahidaProject::ChooseDialog::VerticalMenu::RenderPageInfo(int pageIdx){
	int startIdx = pageIdx * m_OPTIONS_PER_PAGE;
	int endIdx = std::min(startIdx + m_OPTIONS_PER_PAGE, (int)m_options.size());
	std::wcout << L"[ " << startIdx + 1 << L"-" << endIdx << L" / " << m_options.size() << L" ]";
}

void NahidaProject::ChooseDialog::VerticalMenu::ScrollConsole(){
	int totalLines = 2 + std::min(m_OPTIONS_PER_PAGE, (int)m_options.size()) + 2;
	for (int i = 0; i < totalLines; i++) {
		std::cout << "\n";
	}
	MoveConsoleCursorUp(totalLines);
}

void NahidaProject::ChooseDialog::VerticalMenu::RenderFooter(){
	if (!m_B_USE_PAGING && m_instruction == NO_INSTRUCTION) {
		return;
	}

	MoveConsoleCursorDown( std::min(m_OPTIONS_PER_PAGE, (int)m_options.size()) + 1 + 1);
	std::cout << "-";
	ClearLine();
	MoveConsoleCursorDown(1);
	ClearLine();
	MoveConsoleCursorUp(1);

	for (int i = 0; i < std::max(m_title.length(), m_instruction.length() + 1); i++) {
		std::wcout << L"-";
	}
	std::wcout << std::endl;

	if (m_instruction != NO_INSTRUCTION) {
		RenderInstruction();
		std::wcout << L" | ";
	}
	if (m_B_USE_PAGING) {
		RenderPageInfo(m_currentPageIdx);
	}
	std::wcout << std::endl;

	// move console cursor to line of footer, and delete
	MoveConsoleCursorUp(std::min(m_OPTIONS_PER_PAGE, (int)m_options.size()) + 1 + 1 + 2);
}

void NahidaProject::ChooseDialog::VerticalMenu::RenderTitle(){
	std::wcout << m_title << std::endl;
	for (int i = 0; i < m_title.length(); i++) {
		std::wcout << L"-";
	}
	std::wcout << std::endl;
}

void NahidaProject::ChooseDialog::VerticalMenu::RenderOption(int optIdx){
	MoveConsoleCursorDown(optIdx % m_OPTIONS_PER_PAGE);
	ClearLine();
	std::wcout << (m_menuCursorPos == optIdx % m_OPTIONS_PER_PAGE ? m_cursorStyle : L' ');
	std::wcout << L" [" << (m_options[optIdx].IsSelected() ? L'*' : L' ') << L"] ";
	std::wcout << TruncateString(m_options[optIdx]._displayName, GetConsoleLineSize() - 7);
	std::wcout << std::endl;
	MoveConsoleCursorUp(optIdx % m_OPTIONS_PER_PAGE + 1);
}

BOOL NahidaProject::ChooseDialog::VerticalMenu::IsOptionInPage(int optIdx, int pageIdx) {
	int startIdx = pageIdx * m_OPTIONS_PER_PAGE;
	int endIdx = std::min(startIdx + m_OPTIONS_PER_PAGE, (int)m_options.size());

	if (optIdx < endIdx && optIdx >= startIdx) {
		return TRUE;
	}
	return FALSE;
}

int NahidaProject::ChooseDialog::VerticalMenu::GetNumOptionsInPage(int pageIdx) {
	return std::min(m_OPTIONS_PER_PAGE,
		(int)m_options.size() - pageIdx * m_OPTIONS_PER_PAGE);
}

void NahidaProject::ChooseDialog::VerticalMenu::DeletePage() {
	int numOptsInPage;
	if (m_B_USE_PAGING) {
		numOptsInPage = std::max(GetNumOptionsInPage(m_currentPageIdx), m_OPTIONS_PER_PAGE);
	}
	else {
		numOptsInPage = GetNumOptionsInPage(m_currentPageIdx);
	}
	for (int i = 0; i < numOptsInPage; i++) {
		ClearLine();
		MoveConsoleCursorDown(1);
	}
	MoveConsoleCursorUp(numOptsInPage);
}

void NahidaProject::ChooseDialog::VerticalMenu::RenderPage(int pageIdx) {
	DeletePage();
	int numOptionsInPage = GetNumOptionsInPage(pageIdx);

	if (m_menuCursorPos > numOptionsInPage - 1) {
		m_menuCursorPos = numOptionsInPage - 1;
	}

	for (int i = pageIdx * m_OPTIONS_PER_PAGE;
		i < pageIdx * m_OPTIONS_PER_PAGE + numOptionsInPage;
		i++
		) {
		RenderOption(i);
	}
}

void NahidaProject::ChooseDialog::VerticalMenu::HideMenuCursor() {
	m_menuCursorPos = -1;
	RenderPage(m_currentPageIdx);
}

void NahidaProject::ChooseDialog::VerticalMenu::DeleteDescription(){
	MoveConsoleCursorDown(std::min(m_OPTIONS_PER_PAGE, (int)m_options.size())+ 1);
	ClearLine();
	MoveConsoleCursorUp(std::min(m_OPTIONS_PER_PAGE, (int)m_options.size())+ 1);
}

void NahidaProject::ChooseDialog::VerticalMenu::RenderDescription(int optIdx){
	DeleteDescription();

	MoveConsoleCursorDown(std::min(m_OPTIONS_PER_PAGE, (int)m_options.size()) + 1);
	std::wcout << TruncateString(m_options[optIdx]._description, GetConsoleLineSize()) << std::endl;
	MoveConsoleCursorUp(std::min(m_OPTIONS_PER_PAGE, (int)m_options.size()) + 2);
}

void NahidaProject::ChooseDialog::CheckboxMenu::AddOption(const std::wstring& optDisplayName, const std::wstring& optDescription, BOOL isSelectedByDefault) {
	VerticalMenu::AddOption(optDisplayName, optDescription);
	if (isSelectedByDefault) {
		ToggleOption(m_options.back());
	}
}

void NahidaProject::ChooseDialog::CheckboxMenu::Execute(){
	char keyPress;
	BOOL finitoLaComedia = FALSE;

	HideConsoleCursor();
	ScrollConsole();
	RenderTitle();
	RenderPage(0);
	RenderDescription(0);
	RenderFooter();

	do
	{
		ClearInputBuffer();
		keyPress = _getch();
		switch (keyPress)
		{
		case KEY_SPACEBAR:
			ToggleOption(m_options[m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos]);
			break;
		case KEY_ARROW_UP:
			if (m_menuCursorPos > 0) {
				m_menuCursorPos--;
				RenderOption(m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos + 1);
				break;
			}
			continue;
		case KEY_ARROW_DOWN:
			if (m_menuCursorPos < GetNumOptionsInPage(m_currentPageIdx) - 1) {
				m_menuCursorPos++;
				RenderOption(
					m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos - 1);
				break;
			}
			continue;
		case KEY_ARROW_LEFT:
			if (m_B_USE_PAGING && m_currentPageIdx > 0) {
				m_currentPageIdx--;
				RenderPage(m_currentPageIdx);
				RenderFooter();
				break;
			}
			continue;
		case KEY_ARROW_RIGHT:
			if (m_B_USE_PAGING && m_currentPageIdx < m_totalPages - 1) {
				m_currentPageIdx++;
				RenderPage(m_currentPageIdx);
				RenderFooter();
				break;
			}
			continue;
		case KEY_ENTER:
			finitoLaComedia = TRUE;
			break;
		default:
			continue;
		}

		RenderOption(m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos);
		RenderDescription(m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos);

	} while (!finitoLaComedia);

	MoveConsoleCursorDown(GetNumOptionsInPage(m_currentPageIdx) + 5);
	ShowConsoleCursor();
}

void NahidaProject::ChooseDialog::RadioMenu::Execute() {
	char keyPress;
	BOOL finitoLaComedia = FALSE;

	HideConsoleCursor();
	ScrollConsole();
	RenderTitle();
	RenderPage(0);
	RenderDescription(0);
	RenderFooter();

	do
	{
		ClearInputBuffer();
		keyPress = _getch();
		switch (keyPress)
		{
		case KEY_SPACEBAR:
			if (m_selectedOptIdx != -1) {
				UnselectOption(m_options[m_selectedOptIdx]);
				if (IsOptionInPage(m_selectedOptIdx, m_currentPageIdx)) {
					RenderOption(m_selectedOptIdx);
				}
			}
			ToggleOption(m_options[m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos]);
			m_selectedOptIdx = m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos;
			break;
		case KEY_ARROW_UP:
			if (m_menuCursorPos > 0) {
				m_menuCursorPos--;
				RenderOption(m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos + 1);
				break;
			}
			continue;
		case KEY_ARROW_DOWN:
			if (m_menuCursorPos < GetNumOptionsInPage(m_currentPageIdx) - 1) {
				m_menuCursorPos++;
				RenderOption(m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos - 1);
				break;
			}
			continue;
		case KEY_ARROW_LEFT:
			if (m_currentPageIdx > 0) {
				m_currentPageIdx--;
				RenderPage(m_currentPageIdx);
				RenderFooter();
				break;
			}
			continue;
		case KEY_ARROW_RIGHT:
			if (m_currentPageIdx < m_totalPages - 1) {
				m_currentPageIdx++;
				RenderPage(m_currentPageIdx);
				RenderFooter();
				break;
			}
			continue;
		case KEY_ENTER:
			if (m_selectedOptIdx != -1) {
				finitoLaComedia = TRUE;
			}
			break;
		default:
			continue;
		}

		RenderOption(m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos);
		RenderDescription(m_currentPageIdx * m_OPTIONS_PER_PAGE + m_menuCursorPos);

	} while (!finitoLaComedia);

	MoveConsoleCursorDown(GetNumOptionsInPage(m_currentPageIdx) + 5);
	ShowConsoleCursor();
}

void NahidaProject::ChooseDialog::HorizontalMenu::ScrollConsole() {
	int totalLines = 1 + 1 + 2;
	for (int i = 0; i < totalLines; i++) {
		std::cout << "\n";
	}
	MoveConsoleCursorUp(totalLines);
}

void NahidaProject::ChooseDialog::HorizontalMenu::RenderTitle() {
	std::wcout << m_title << L"  ";
}

void NahidaProject::ChooseDialog::HorizontalMenu::RenderOption(int optIdx){
	int optStartPos = 0;
	for (int i = 0; i < optIdx; i++) {
		optStartPos += TruncateString(m_options[i]._displayName, m_maxOptLength).size();
		optStartPos += 4;
		if (i < m_options.size() - 1) {
			optStartPos += 3;
		}
	}
	MoveConsoleCursorRight(optStartPos);

	// display option
	std::wstring truncatedOpt = TruncateString(m_options[optIdx]._displayName, m_maxOptLength);
	int optLength = truncatedOpt.size() + 4;
	ClearRight(optLength);

	if (m_menuCursorPos == optIdx) {
		std::wcout << "[ " << truncatedOpt << " ]";
	}
	else {
		std::wcout << "  " << truncatedOpt << "  ";
	}

	if (optIdx < m_options.size() - 1) {
		std::wcout << L" \| ";
		optLength += 3;
	}

	MoveConsoleCursorLeft(optStartPos + optLength);
}

void NahidaProject::ChooseDialog::HorizontalMenu::DeleteDescription(){
	MoveConsoleCursorDown(2);
	ClearLine();
}

void NahidaProject::ChooseDialog::HorizontalMenu::RenderDescription(int optIdx){
	DeleteDescription();
	std::wcout << TruncateString(m_options[m_menuCursorPos]._description, GetConsoleLineSize()) << std::endl;

	MoveConsoleCursorUp(3);
	MoveConsoleCursorRight(m_title.size() + 2);
}

void NahidaProject::ChooseDialog::PromptMenu::Execute(){
	char keyPress;
	BOOL finitoLaComedia = FALSE;

	HideConsoleCursor();
	ScrollConsole();
	RenderTitle();

	for (int i = 0; i < m_options.size(); i++) {
		RenderOption(i);
	}
	RenderDescription(0);

	do
	{
		ClearInputBuffer();
		keyPress = _getch();
		switch (keyPress)
		{
		case KEY_ARROW_LEFT:
			if (m_menuCursorPos > 0) {
				m_menuCursorPos--;
				RenderOption(m_menuCursorPos + 1);
				break;
			}
			continue;
		case KEY_ARROW_RIGHT:
			if (m_menuCursorPos < m_options.size() - 1) {
				m_menuCursorPos++;
				RenderOption(m_menuCursorPos - 1);
				break;
			}
			continue;
		case KEY_SPACEBAR:
		case KEY_ENTER:
			SelectOption(m_options[m_menuCursorPos]);
			finitoLaComedia = TRUE;
			break;
		default:
			continue;
		}

		RenderOption(m_menuCursorPos);
		RenderDescription(m_menuCursorPos);

	} while (!finitoLaComedia);

	DeleteDescription();
	ShowConsoleCursor();
}